package com.zwb.mongostudy.config;

import com.mongodb.MongoClientSettings;
import com.mongodb.client.MongoClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration;
import org.springframework.boot.autoconfigure.domain.EntityScanner;
import org.springframework.boot.autoconfigure.mongo.*;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.ssl.SslBundles;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mapping.model.FieldNamingStrategy;
import org.springframework.data.mongodb.MongoDatabaseFactory;
import org.springframework.data.mongodb.MongoManagedTypes;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.SimpleMongoClientDatabaseFactory;
import org.springframework.data.mongodb.core.convert.*;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;

import java.util.*;
import java.util.stream.Collectors;

/**
 * Description: Mongo动态数据源配置
 * Copyright: (c) 2022 SunTime Co'Ltd Inc. All rights reserved.<br/>
 *
 * @author zhongwb
 * @version 1.0
 * @date 2025/03/27 10:42
 * @since JDK11
 */
@Configuration
@EnableConfigurationProperties({MultipleDataSourcesProperties.class})
@ImportAutoConfiguration(exclude = {MongoAutoConfiguration.class, MongoDataAutoConfiguration.class})
public class MongoDBMultiDataSourceConfiguration {

    /**
     * 建立与MongoDB服务的连接所需的详细信息。
     */
    @Bean("connectionDetailsMap")
    Map<String, MongoConnectionDetails> connectionDetailsMap(MultipleDataSourcesProperties dataSourcesProperties) {
        return dataSourcesProperties.getMultipleDataSources().entrySet().stream()
                .collect(Collectors.toMap(Map.Entry::getKey, entity ->
                        new PropertiesMongoConnectionDetails(entity.getValue())));
    }

    /**
     * 各种设置来控制MongoClient的行为。
     */
    @Bean
    MongoClientSettings mongoClientSettings() {
        return MongoClientSettings.builder().build();
    }

    /**
     * 回调接口，可以由希望通过MongoClientSettings. Builder自定义com. Mongodb. MongoClientSettings. 同时保留默认自动配置的bean实现。
     */
    @Bean
    Map<String, MongoClientSettingsBuilderCustomizer> standardMongoClientSettingsBuilderCustomizerMap(MultipleDataSourcesProperties dataSourcesProperties,
                                                                                                      Map<String, MongoConnectionDetails> connectionDetailsMap,
                                                                                                      ObjectProvider<SslBundles> sslBundles) {
        return dataSourcesProperties.getMultipleDataSources().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> {
            MongoProperties properties = entry.getValue();
            return new StandardMongoClientSettingsBuilderCustomizer(connectionDetailsMap.get(entry.getKey()).getConnectionString(),
                    properties.getUuidRepresentation(), properties.getSsl(), sslBundles.getIfAvailable());
        }));
    }

    /**
     * MongoDB集群的客户端表示形式。实例可以表示独立的MongoDB实例、副本集或分片集群。此类的实例负责维护集群的最新状态，并可能缓存与此相关的资源，包括用于监视的后台线程和连接池。
     * 此类的实例用作MongoDatabase实例的工厂。
     * 这个类的实例可以通过MongoClients工厂创建。
     */
    @Bean
    public Map<String, MongoClient> mongoClientMap(Map<String, MongoClientSettingsBuilderCustomizer> builderCustomizerMap,
                                                   MongoClientSettings mongoClientSettings) {
        return builderCustomizerMap.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> {
            MongoClientSettingsBuilderCustomizer builderCustomizers = entry.getValue();
            return new MongoClientFactory(Collections.singletonList(builderCustomizers)).createMongoClient(mongoClientSettings);
        }));
    }

    /**
     * 用于创建MongoDatabase实例的工厂的接口。
     */
    @Bean
    public Map<String, MongoDatabaseFactory> mongoDatabaseFactory(Map<String, MongoClient> mongoClientMap, MultipleDataSourcesProperties dataSourcesProperties) {
        return dataSourcesProperties.getMultipleDataSources().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> {
            MongoClient mongoClient = mongoClientMap.get(entry.getKey());
            return new SimpleMongoClientDatabaseFactory(mongoClient, entry.getValue().getMongoClientDatabase());
        }));
    }

    /**
     * mongo托管类型
     */
    @Bean
    static MongoManagedTypes mongoManagedTypes(ApplicationContext applicationContext) throws ClassNotFoundException {
        return MongoManagedTypes.fromIterable(new EntityScanner(applicationContext).scan(Document.class));
    }


    /**
     * 默认实现的MappingContext对于MongoDB使用基本操作实体和BasicMongoPersistentProperty作为主要的抽象。
     */
    @Bean
    Map<String, MongoMappingContext> mongoMappingContext(MultipleDataSourcesProperties dataSourcesProperties,
                                                         MongoCustomConversions conversions,
                                                         MongoManagedTypes managedTypes) {
        return dataSourcesProperties.getMultipleDataSources().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> {
            PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
            MongoMappingContext context = new MongoMappingContext();
            map.from(entry.getValue().isAutoIndexCreation()).to(context::setAutoIndexCreation);
            context.setManagedTypes(managedTypes);
            Class<?> strategyClass = entry.getValue().getFieldNamingStrategy();
            if (strategyClass != null) {
                context.setFieldNamingStrategy((FieldNamingStrategy) BeanUtils.instantiateClass(strategyClass));
            }
            context.setSimpleTypeHolder(conversions.getSimpleTypeHolder());
            return context;
        }));
    }

    /**
     * mongo自定义转换
     */
    @Bean
    MongoCustomConversions mongoCustomConversions() {
        return new MongoCustomConversions(Collections.emptyList());
    }

    /**
     * 映射mongo转换器
     */
    @Bean
    public Map<String, MappingMongoConverter> mongoConverter(Map<String, MongoDatabaseFactory> databaseFactoryMap,
                                                             Map<String, MongoMappingContext> mongoMappingContextMap,
                                                             MongoCustomConversions mongoCustomConversions) {
        return databaseFactoryMap.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> {
            MongoDatabaseFactory databaseFactory = entry.getValue();
            DbRefResolver defaultDbRefResolver = (databaseFactory != null) ? new DefaultDbRefResolver(databaseFactory)
                    : NoOpDbRefResolver.INSTANCE;
            MappingMongoConverter converter = new MappingMongoConverter(defaultDbRefResolver, mongoMappingContextMap.get(entry.getKey()));
            // 设置转换器
            converter.setCustomConversions(mongoCustomConversions);
            converter.setTypeMapper(new DefaultMongoTypeMapper(null));
            converter.afterPropertiesSet();
            return converter;
        }));
    }

    /**
     * MongoTemplateManager： mongo的模板管理器
     */
    @Bean
    public MongoTemplateManager mongoTemplateManager(
            MultipleDataSourcesProperties dataSourcesProperties,
            Map<String, MongoDatabaseFactory> databaseFactoryMap,
            Map<String, MappingMongoConverter> converterMap,
            ConfigurableListableBeanFactory beanFactory) {
        HashMap<String, MongoTemplate> templateHashMap = new HashMap<>();
        for (String dataBaseName : dataSourcesProperties.getMultipleDataSources().keySet()) {
            MappingMongoConverter mappingMongoConverter = converterMap.get(dataBaseName);
            MongoDatabaseFactory mongoDatabaseFactory = databaseFactoryMap.get(dataBaseName);
            templateHashMap.put(dataBaseName, new MongoTemplate(mongoDatabaseFactory, mappingMongoConverter));
            beanFactory.registerSingleton((dataBaseName + dataSourcesProperties.getTemplateNameSuffix()).trim(),
                    templateHashMap.get(dataBaseName));
        }
        MongoTemplate defaultDataSource = templateHashMap.get(dataSourcesProperties.getDefaultDataSource());
        return new DefaultMongoTemplateManager(defaultDataSource, templateHashMap);
    }

    /**
     * 默认的mongoTemplate
     */
    @Bean
    public MongoTemplate mongoTemplate(MongoTemplateManager mongoTemplateManager) {
        return mongoTemplateManager.getDefaultTemplate().orElseThrow(() -> new IllegalStateException("No default mongoTemplate found"));
    }

}
